/**
 * 
 */
package org.wicket.jbpm.engine.utils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.hibernate.proxy.HibernateProxy;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.def.Transition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
import org.jbpm.taskmgmt.def.Task;
import org.jbpm.taskmgmt.exe.TaskInstance;
import org.safehaus.uuid.UUIDGenerator;
import org.wicket.jbpm.engine.core.BPMEngineRegistry;
import org.wicket.jbpm.engine.workflow.WfDefinition;
import org.wicket.jbpm.engine.workflow.WfInstance;
import org.wicket.jbpm.engine.workflow.WfNode;
import org.wicket.jbpm.engine.workflow.WfPath;
import org.wicket.jbpm.engine.workflow.WfTask;
import org.wicket.jbpm.engine.workflow.WfTaskDefinition;
import org.wicket.jbpm.engine.workflow.WfTaskState;
import org.wicket.jbpm.engine.workflow.WfTransition;

/**
 * @author Emmanuel Nollase - emanux
 * created: Jun 1, 2009 - 7:16:42 PM
 */
public final class JbpmEngineUtils
{

    private final static String WORKFLOW_PATH_SEPERATOR = "-";
    private final static String WORKFLOW_TOKEN_SEPERATOR = "@";

    /**
     * create task definition
     * @param task
     * @return
     */
    public static WfTaskDefinition createWorkflowTaskDefinition(Task task)
    {
	final WfTaskDefinition taskDef = new WfTaskDefinition();
	taskDef.id = task.getId();
	taskDef.name = task.getName();
	Node node = (task.getStartState() == null ? task.getTaskNode() : task
		.getStartState());
	taskDef.node = createWorkflowNode(node);
	// taskDef.metadata = getTaskDefinition(task);
	return taskDef;
    }

    /**
     * create workflow task
     * @param engineId
     * @param task
     * @return
     */
    public static WfTask createWorkflowTask(String engineId, TaskInstance task)
    {
	final String processName = task.getTask().getProcessDefinition()
		.getName();
	final WfTask wfTask = new WfTask();
	wfTask.tid = task.getId();
	wfTask.id = createGlobalId(engineId, new Long(task.getId()).toString());
	wfTask.name = task.getName();
	wfTask.description = task.getDescription();
	wfTask.title = processName + " - " + wfTask.name;
	wfTask.path = createWorkflowPath(task.getToken());
	wfTask.state = getWorkflowTaskState(task);
	wfTask.definition = createWorkflowTaskDefinition(task.getTask());
	wfTask.createDt = task.getCreate();
	wfTask.endDt = task.getEnd();
	// wfTask.comments = task.getI
	// wfTask.applicationNo = task.getVariable("applicationNo").toString();
	// wfTask.custName = task.getVariable("CUSTNAME").toString();

	final Map<String, Serializable> taskVariables = getTaskVariables(task);
	wfTask.variables = taskVariables;

	return wfTask;
    }

    /**
     * create workflow instance
     * @param instance
     * @return
     */
    public static WfInstance createWorkflowInstance(ProcessInstance instance)
    {

	final WfInstance wfInstance = new WfInstance();
	wfInstance.id = String.valueOf(instance.getId());
	wfInstance.definition = getWorkflowDefinition(instance
		.getProcessDefinition());
	wfInstance.active = !instance.hasEnded();
	wfInstance.startDate = instance.getStart();
	wfInstance.endDate = instance.getEnd();
	wfInstance.key = instance.getKey();

	return wfInstance;
    }

    /**
     * transform a JBPM Object into Igenko Object
     * @param processDef
     * @return the preocess definition associtaed to the process definitio
     */
    public static WfDefinition getWorkflowDefinition(
	    ProcessDefinition processDef)
    {
	WfDefinition result = null;
	if (processDef != null)
	{
	    result = new WfDefinition(String.valueOf(processDef.getId()),
		    processDef.getName(), String.valueOf(processDef
			    .getVersion()), processDef.getName(), processDef
			    .getDescription());
	}
	return result;

    }

    /**
     * create workflow path
     * @param token
     * @return
     */
    public static WfPath createWorkflowPath(Token token)
    {
	final WfPath path = new WfPath();
	String tokenId = token.getFullName().replace("/",
		WORKFLOW_TOKEN_SEPERATOR); // String.valueOf(token.getId());
	// path.id = tokenId;
	path.id = token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR
		+ tokenId;
	path.instance = createWorkflowInstance(token.getProcessInstance());
	path.node = createWorkflowNode(token.getNode());
	path.active = token.hasEnded();
	return path;
    }

    /**
     * create workflow node
     * @param node
     * @return
     */
    public static WfNode createWorkflowNode(Node node)
    {
	final WfNode wfNode = new WfNode();
	wfNode.name = node.getName();
	wfNode.title = wfNode.name;
	wfNode.description = wfNode.title;
	wfNode.type = getRealNode(node).getClass().getSimpleName();
	// TODO: Is there a formal way of determing if task node?
	// wfNode.setTaskNode(workflowNode.getType().equals(WorkflowConstant.TASK_NODE_TYPE));
	wfNode.isTaskNode = wfNode.type.equals("TaskNode");
	final List<Transition> transitions = node.getLeavingTransitions();
	WfTransition[] wfTransitions = null;
	if (transitions == null)
	{
	    wfTransitions = new WfTransition[0];
	} else
	{
	    wfTransitions = new WfTransition[transitions.size()];
	}

	if (transitions != null)
	{
	    int i = 0;
	    for (Transition transition : (List<Transition>) transitions)
	    {
		wfTransitions[i] = createWorkflowTransition(transition);
	    }

	}
	wfNode.transitions = wfTransitions;
	return wfNode;
    }

    /**
     * Helper to retrieve the real jBPM Node
     * @param node - Node
     * @return real Node (i.e. the one that's not a Hibernate proxy)
     */
    public static Node getRealNode(Node node)
    {
	if (node instanceof HibernateProxy)
	{
	    Node realNode = (Node) ((HibernateProxy) node)
		    .getHibernateLazyInitializer().getImplementation();
	    return realNode;
	} else
	{
	    return node;
	}
    }

    /**
     * Create a Workflow Transition
     * @param transition
     *            JBoss JBPM Transition
     * @return Workflow Transition
     */
    public static WfTransition createWorkflowTransition(Transition transition)
    {
	final WfTransition wfTransition = new WfTransition();
	wfTransition.id = String.valueOf(transition.getId());
	// wfTransition.name = transition.getName();
	Node node = transition.getFrom();
	wfTransition.isDefault = node.getDefaultLeavingTransition().equals(
		transition);
	wfTransition.title = transition.getName();
	wfTransition.description = wfTransition.title;
	// wfTransition.setTo(transition.getTo().getName());
	// wfTransition.setFrom(transition.getFrom().getName());
	return wfTransition;
    }

    /**
     * Get the Workflow Task State for the specified JBoss JBPM Task
     * @param task
     *            task
     * @return task state
     */
    public static WfTaskState getWorkflowTaskState(TaskInstance task)
    {
	if (task.hasEnded())
	{
	    return WfTaskState.COMPLETED;
	} else
	{
	    return WfTaskState.IN_PROGRESS;
	}
    }

    /**
     * create a map representing the task variable
     * @param task
     * @return the map representing the task variables
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Serializable> getTaskVariables(TaskInstance task)
    {
	final Map<String, Serializable> taskVariables = new HashMap<String, Serializable>();
	final Map<String, Serializable> vars = task.getVariables();
	final Set varSet = vars.entrySet();
	for (Iterator iter = varSet.iterator(); iter.hasNext();)
	{
	    Entry<String, Object> var = (Entry<String, Object>) iter.next();
	    String name = var.getKey();
	    Object value = var.getValue();
	    taskVariables.put(name, (Serializable) value);

	}
	return taskVariables;
    }
    
    /**
     * return remaining task for a particular process instance
     * @param possibleTask
     * @param completedTask
     * @param task
     * @return
     */
    public static List<String> getNextTasks(Set<String> possibleTask,List<String> completedTask,Map<String,Task> task)
    {
		final List<String> nextTasks = new ArrayList<String>();
		
		final Set<String> tmpPossibleTask = new HashSet<String>(possibleTask);
		final List<String> tmpActiveTask = new ArrayList<String>(completedTask);
		final Map<String, Task> mapTask = new HashMap<String, Task>(task);
		
		for(String s : tmpActiveTask)
		{
			if ( tmpPossibleTask.contains(s) )
			{
				tmpPossibleTask.remove(s);
			}
		}
		Iterator<String> ite = tmpPossibleTask.iterator();
		while (ite.hasNext()) {
			final String string =  ite.next();
			final Task taskLeft = mapTask.get(string);
			nextTasks.add(taskLeft.getDescription());
		}
		return nextTasks;
    }

    public static long getJbpmId(String id)
    {
	try
	{
	    String theLong = id;
	    return new Long(theLong);
	} catch (NumberFormatException e)
	{
	    throw new IllegalArgumentException("Format of id '" + id
		    + "' is invalid", e);
	}
    }

    public static String createGlobalId(String engineId, String localId)
    {
	return BPMEngineRegistry.createGlobalId(engineId, localId);
    }

    public static String genearteUID()
    {
	return UUIDGenerator.getInstance().generateRandomBasedUUID().toString();
    }
    
    private JbpmEngineUtils()
    {
    }
}
